
package org.sopt.dev.ImgKnight.monitor;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringTokenizer;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.sopt.dev.ImgKnight.monitor.chart.AgentChartSet;
import org.sopt.dev.ImgKnight.monitor.chart.ChartDataGenerator;
import org.sopt.dev.ImgKnight.monitor.chart.FrontierChartSet;
import org.sopt.dev.ImgKnight.monitor.dialog.AlertDialog;
import org.sopt.dev.ImgKnight.monitor.dialog.ConnectionDialog;
import org.sopt.dev.ImgKnight.monitor.gui.builder.ContentsBuilder;
import org.sopt.dev.ImgKnight.monitor.gui.builder.CoolBarBuilder;
import org.sopt.dev.ImgKnight.monitor.gui.builder.MenuBuilder;
import org.sopt.dev.ImgKnight.monitor.gui.builder.TrayBuilder;
import org.sopt.dev.ImgKnight.monitor.nio.Peer;
import org.sopt.dev.ImgKnight.monitor.operation.Operations;
import org.sopt.dev.ImgKnight.monitor.resource.ResourceManager;

public class Monitor {

	private static ResourceBundle resourceBundle = ResourceBundle.getBundle("monitor");
	
	private String entryAddress = null;
	
	private String portNum = null;
	
	private Display display = null; 
	
	private Shell shell = null;
	
	private boolean minToTray = false;
	
	private int frequency = 1;
	
	private MenuBuilder menuBuilder = null;
	
	private CoolBarBuilder coolBarBuilder = null;
	
	private ContentsBuilder contentsBuilder = null;
	
	private TrayBuilder trayBuilder = null;
	
	private Peer frontierPeer = null;
	
	private List<Peer> agentPeers = null;
	
	private ChartDataGenerator dataGenerator = null;
	
	public Shell open(String entryAddress, String portNum, Display display) {
		this.entryAddress = entryAddress;
		this.portNum = portNum;
		this.display = display;
		shell = new Shell();
		shell.setText("CrawlerMonitor");
		shell.setImage(ResourceManager.loadImage(display, "ServiceManager.png"));
		GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 2;
		gridLayout.marginHeight = gridLayout.marginWidth = 0;
		shell.setLayout(gridLayout);
		
		ConnectionDialog connDlg = new ConnectionDialog(display, shell);
		connDlg.open(entryAddress, portNum);
		InetSocketAddress servAddress = connDlg.getServAddress();
		if( servAddress == null ) {
			AlertDialog.alert(shell, "Invalid Parameter", "Input server host's IP and Port number");
			return null;
		}
		
		List<String> agentList = createFirstConnection(servAddress);
		if(  agentList == null ) {
			shell.close();
			return null;
		}
		if( !createShellContents(servAddress, agentList)) {
			shell.close();
			return null;
		}
		shell.open();
		dataGenerator = new ChartDataGenerator(this, 1000);
		dataGenerator.open();
		return shell;
	}
	
	public synchronized void close() {
		if( dataGenerator != null )
			dataGenerator.close();
		if( frontierPeer != null )
			frontierPeer.close();
		for(Peer agent : agentPeers)
			agent.close();
	}
	
	private List<String> createFirstConnection(InetSocketAddress servAddress) {
		List<String> agentList = null;
		Socket synchSocket = null;
		try {
			synchSocket = new Socket();
			synchSocket.connect(servAddress);	
			BufferedReader reader = new BufferedReader(new InputStreamReader(synchSocket.getInputStream(), "UTF-8"));
			PrintWriter writer = new PrintWriter(new OutputStreamWriter(synchSocket.getOutputStream(), "UTF-8"));
			writer.write(Operations.opInfoAgent+"\r\n");
			writer.flush();
			
			String response = reader.readLine();
			response = Operations.sliceHeder(response);
			if( response == null ) {
				AlertDialog.alert(shell, "Invalid Response", "Operation: "+Operations.opInfoAgent+", Response: "+response);
				return null;
			}
			
			agentList = new LinkedList<String>();
			if( !response.equals("") && response != null ) {
				StringTokenizer tokenizer = new StringTokenizer(response, "/");
				while( tokenizer.hasMoreTokens() ) {
					String agentAddr = "/"+tokenizer.nextToken();
					agentList.add(agentAddr);
				}
			}
		} catch (Exception e) {
			AlertDialog.alert(shell, e.toString(), e.getMessage());
			return null;
		} finally {
			if( synchSocket != null ) {
				try { synchSocket.close(); } 
				catch (IOException e) { }
			}
		}
		return agentList;
	}
	
	private boolean createShellContents(InetSocketAddress servAddress, List<String> agentList) {
		frontierPeer = new Peer(this);
		ChannelFuture future = frontierPeer.open(servAddress);
		if( !future.isSuccess() ) {
			AlertDialog.alert(shell, "Connection Error", future.getCause().getMessage());
			return false;
		}
		
		agentPeers = new LinkedList<Peer>();
		for(String agentAddress : agentList) {
			Peer agentPeer = new Peer(this);
			future = agentPeer.open(new InetSocketAddress(agentAddress.substring(1, agentAddress.indexOf(":")), 4531));
			if( !future.isSuccess() ) {
				AlertDialog.alert(shell, "Connection Error", future.getCause().getMessage());
				return false;
			}
			agentPeers.add(agentPeer);
		}
		
		agentList.add(0, "/"+frontierPeer.getIpAddress());
		Menu bar = new Menu(shell, SWT.BAR|SWT.LEFT_TO_RIGHT);
		shell.setMenuBar(bar);
		menuBuilder = new MenuBuilder(this, bar);
		menuBuilder.build();
		coolBarBuilder = new CoolBarBuilder(this);
		coolBarBuilder.build();
		contentsBuilder = new ContentsBuilder(this, agentPeers);
		contentsBuilder.build();
		trayBuilder = new TrayBuilder(this);
		trayBuilder.build();
		return true;
	}
	
	public void parseResponse(Channel peer, String response) {
		String header = Operations.getHeader(response);
		response = Operations.sliceHeder(response);
		response = response.replace(",", "");
		
		if( contentsBuilder.isFrontierPeer(peer) ) {
			FrontierChartSet frontierChartSet = contentsBuilder.getFrontierChartSet();
			if( header.equals(Operations.opInfoCondition) ) {
				StringTokenizer tokenizer = new StringTokenizer(response, ":");
				while( tokenizer.hasMoreTokens() ) {
					@SuppressWarnings("unused")
					double maxMemory = Double.parseDouble(tokenizer.nextToken());
					double totalMemory = Double.parseDouble(tokenizer.nextToken());
					double freeMemory = Double.parseDouble(tokenizer.nextToken());
					int highPrioMemPushedCount = Integer.parseInt(tokenizer.nextToken());
					int memPushedCount = Integer.parseInt(tokenizer.nextToken());
					int cachePushedCount = Integer.parseInt(tokenizer.nextToken());
					int diskPushedCount = Integer.parseInt(tokenizer.nextToken());
					int popedCount = Integer.parseInt(tokenizer.nextToken());
					int duplicatedCount = Integer.parseInt(tokenizer.nextToken());
					int discardCount = Integer.parseInt(tokenizer.nextToken());
					int highPrioSiteMapMultiLevel = Integer.parseInt(tokenizer.nextToken());
					int siteMapMultiLevel = Integer.parseInt(tokenizer.nextToken());
					int fpDocCurrSize = Integer.parseInt(tokenizer.nextToken());
					int fpDocFlusedSize = Integer.parseInt(tokenizer.nextToken());
					
					frontierChartSet.updateMemotyStatus(totalMemory, freeMemory);
					frontierChartSet.updatePushedUrlStatus(highPrioMemPushedCount, 
							memPushedCount, cachePushedCount, diskPushedCount, popedCount);
					frontierChartSet.updateDropStatus(duplicatedCount, discardCount);
					frontierChartSet.updateSiteMapMultiLevelStatus(highPrioSiteMapMultiLevel, siteMapMultiLevel);
					frontierChartSet.updateFpDocStatus(fpDocCurrSize, fpDocFlusedSize);
				}
			}
			else if( header.equals(Operations.opInfoAgentTrafic) ) {
				int trafic = Integer.parseInt(response);
				frontierChartSet.updateTraficStatus(trafic);
			}
			else if( header.equals(Operations.opInfoHighPrioUrl) ) {
				contentsBuilder.setResponseForHighPrioUrlText(response);
			}
			else if( header.equals(Operations.opInfoAllowPattern) ) {
				contentsBuilder.setResponseForAllowPatternText(response);
			}
			else if( header.equals(Operations.opInfoDenyPattern) ) {
				contentsBuilder.setResponseForDenyPatternText(response);
			}
			else if( header.startsWith(Operations.opSleep) ) {
				final String result = response;
				display.asyncExec(new Runnable() {
					public void run() {
						MessageBox dialog = new MessageBox(shell, SWT.ICON_INFORMATION);
						dialog.setText("Sleep");
						dialog.setMessage(result);
						dialog.open();
					}
				});
			}
			else if( header.equals(Operations.opAddHighPrioUrl) ) {
				final String result = response.substring(0, response.indexOf(" "));
				final int count = Integer.parseInt(response.substring(response.indexOf(" ")+1));
				display.asyncExec(new Runnable() {
					public void run() {
						MessageBox dialog = new MessageBox(shell, SWT.ICON_INFORMATION);
						dialog.setText("Add HighPrio URL");
						dialog.setMessage(result+": "+count);
						dialog.open();
					}
				});
			}
			else if( header.equals(Operations.opAddAllowPattern) ) {
				final String result = response.substring(0, response.indexOf(" "));
				final int count = Integer.parseInt(response.substring(response.indexOf(" ")+1));
				display.asyncExec(new Runnable() {
					public void run() {
						MessageBox dialog = new MessageBox(shell, SWT.ICON_INFORMATION);
						dialog.setText("Add allow pattern");
						dialog.setMessage(result+": "+count);
						dialog.open();
					}
				});
			}
			else if( header.equals(Operations.opAddDenyPattern) ) {
				final String result = response.substring(0, response.indexOf(" "));
				final int count = Integer.parseInt(response.substring(response.indexOf(" ")+1));
				display.asyncExec(new Runnable() {
					public void run() {
						MessageBox dialog = new MessageBox(shell, SWT.ICON_INFORMATION);
						dialog.setText("Add deny pattern");
						dialog.setMessage(result+": "+count);
						dialog.open();
					}
				});
			}
			else if( header.equals(Operations.opRmUrl) ) {
				final String result = response.substring(0, response.indexOf(" "));
				final int count = Integer.parseInt(response.substring(response.indexOf(" ")+1));
				display.asyncExec(new Runnable() {
					public void run() {
						MessageBox dialog = new MessageBox(shell, SWT.ICON_INFORMATION);
						dialog.setText("Remove URL");
						dialog.setMessage(result+": "+count);
						dialog.open();
					}
				});
			}
		}
		else {
			AgentChartSet agentChartSet = contentsBuilder.getAgentChartSet(peer);
			if( header.equals(Operations.opInfoCondition) ) {
				StringTokenizer tokenizer = new StringTokenizer(response, ":");
				while( tokenizer.hasMoreTokens() ) {
					@SuppressWarnings("unused")
					double maxMemory = Double.parseDouble(tokenizer.nextToken());
					double totalMemory = Double.parseDouble(tokenizer.nextToken());
					double freeMemory = Double.parseDouble(tokenizer.nextToken());
					int reservedUrlCount = Integer.parseInt(tokenizer.nextToken());
					int takeUrlCount = Integer.parseInt(tokenizer.nextToken());
					int discardUrlCount = Integer.parseInt(tokenizer.nextToken());
					int missUrlCount = Integer.parseInt(tokenizer.nextToken());
					int reservedResourceCount = Integer.parseInt(tokenizer.nextToken());
					int takeResourceCount = Integer.parseInt(tokenizer.nextToken());
					int discardResouceCount = Integer.parseInt(tokenizer.nextToken());
					int missResouceCount = Integer.parseInt(tokenizer.nextToken());
					int duplicatedImageCount = Integer.parseInt(tokenizer.nextToken());
					int seenImageCurrentCount = Integer.parseInt(tokenizer.nextToken());
					int seenImageFlushedCount = Integer.parseInt(tokenizer.nextToken());
					
					agentChartSet.updateMemotyStatus(totalMemory, freeMemory);
					agentChartSet.updateFetchedUrlStatus(reservedUrlCount, takeUrlCount, discardUrlCount, missUrlCount);
					agentChartSet.updateFetchedResourceStatus(reservedResourceCount, takeResourceCount, 
							discardResouceCount, missResouceCount);
					agentChartSet.updateDuplicatedImageStatus(duplicatedImageCount);
					agentChartSet.updateSeenImageStatus(seenImageCurrentCount, seenImageFlushedCount);
				}
			}
			else if( header.equals(Operations.opInfoTrafic) ) {
				int trafic = Integer.parseInt(response);
				agentChartSet.updateTraficStatus(trafic);
			}
		}
	}
	
	public Shell getShell() {
		return shell;
	}
	
	public void setShell(Shell shell) {
		this.shell = shell;
	}

	public boolean connect() {
		return false;
	}
	
	public boolean isMinToTray() {
		return minToTray;
	}

	public void setMinToTray(boolean minToTray) {
		this.minToTray = minToTray;
	}

	public synchronized int getFrequency() {
		return frequency;
	}

	public synchronized void setFrequency(int frequency) {
		this.frequency = frequency;
		int inteval = 0;
		if( this.frequency == MenuBuilder.FREQ_HIGH )
			inteval = 500;
		else if( this.frequency == MenuBuilder.FREQ_NORMAL )
			inteval = 1000;
		else
			inteval = 3000;
		dataGenerator.setDataGeneratRatio(inteval);
	}
	
	public Display getDisplay() {
		return display;
	}
	
	public MenuBuilder getMenuBuilder() {
		return menuBuilder;
	}
	
	public ContentsBuilder getContentsBuilder() {
		return contentsBuilder;
	}

	public synchronized Peer getFrontierPeer() {
		return frontierPeer;
	}

	public synchronized Peer[] getAgentPeers() {
		if( agentPeers.size() == 0 )
			return null;
		Peer[] Peers = new Peer[agentPeers.size()];
		agentPeers.toArray(Peers);
		return Peers;
	}
	
	public synchronized void removePeer(Channel channel) {
		class ChnnelCloseAlertRunnable implements Runnable {
			private String title = null;
			private String message = null;
			public ChnnelCloseAlertRunnable(String title, String message) {
				this.title = title;
				this.message = message;
			}
			public void run() {
				try{ AlertDialog.alert(shell, title, message); }
				catch(Exception ignore) { /**/ }
			}
		}
		if( frontierPeer.getChannel().equals(channel) ) {
			display.asyncExec(
					new ChnnelCloseAlertRunnable("Channel reset", 
							"frontier: "+channel.getRemoteAddress().toString()));
			frontierPeer.close();
			frontierPeer = null;
			return;
		}
		for(Peer peer : agentPeers) {
			if( peer.getChannel().equals(channel) ) {
				display.asyncExec(
						new ChnnelCloseAlertRunnable("Channel reset", 
								"agent: "+channel.getRemoteAddress().toString()));
				peer.close();
				return;
			}
		}
	}
	
	public synchronized void writeGcSignal(String peerAddress) {
		for(Peer peer : agentPeers) {
			if( peer.equals(peerAddress) ) {
				Operations.runGC(peer.getChannel());
			}
		}
	}
	
	public static String getResourceString(String key) {
		try {
			return resourceBundle.getString(key);
		} catch (MissingResourceException e) {
			return key;
		} catch (NullPointerException e) {
			return "!" + key + "!";
		}			
	}
	
	public static String getResourceString(String key, Object[] args) {
		try {
			return MessageFormat.format(getResourceString(key), args);
		} catch (MissingResourceException e) {
			return key;
		} catch (NullPointerException e) {
			return "!" + key + "!";
		}
	}

	public String getEntryAddress() {
		return entryAddress;
	}

	public String getPortNum() {
		return portNum;
	}
	
}
